
// Copyright (C) 2007 Richard Uhler
// This file is part of Esiply.
//
// Esiply is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, Version 2,
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include <assert.h>

#include "Boolean.h"
#include "CaseClause.h"
#include "CaseClauses.h"
#include "Completion.h"
#include "Expression.h"
#include "Null.h"
#include "SwitchStatement.h"

SwitchStatement::SwitchStatement(CaseClauses* accs, Statement* def,
        CaseClauses* bccs)
    : m_accs(accs), m_def(def), m_bccs(bccs), m_exp(NULL)
{}

Value* SwitchStatement::execute_internal(Scope* scope) const
{
    // Good luck.
    // Ecma 12.11
    // 1. Evaluate expression
    // 2. Call getValue on the result
    assert(m_exp);
    Value* input = m_exp->evaluate(scope)->getValue();

    // 3. Evaluate the CaseBlock (coming up next)
    // 4. If result is break and res.target is in the current label set,
    // return (normal, res.value, empty). We don't have to do anything.
    // Our base class Statement does all the work for us.
    // 5 Return the result (coming up next)
    
    // Ecma 12.11 (from errata)
    // I honestly have no idea how to implement their algorithm nicely in
    // c++ without using goto, which I refuse to use. Here's what I 
    // believe the jist of it is:
    // Search for the first match in the order: a, b, default
    // Once found, execute and fall through as: a, default, b
    // Which makes for the messy algorithm.

    // Find the first match. Save it's index, and where we found it.
    const int NOT_FOUND_YET = 0;
    const int FOUND_IN_A = 1;
    const int FOUND_IN_B = 2;
    const int FOUND_IN_DEFAULT = 3;
    int index = -1;
    int where = NOT_FOUND_YET;

    // Look in a
    if (m_accs)
    {
        for (int i = 0; i < m_accs->size(); i++)
        {
            Expression* case_exp = m_accs->get(i)->get_expression();
            Value* case_val = case_exp->evaluate(scope)->getValue();
            if (Value::strict_equals(case_val, input)->isTrue())
            {
                // We found a match
                where = FOUND_IN_A;
                index = i;
                break;
            }
        }
    }

    // look in b
    if (where == NOT_FOUND_YET && m_bccs)
    {
        for (int i = 0; i < m_bccs->size(); i++)
        {
            Expression* case_exp = m_bccs->get(i)->get_expression();
            Value* case_val = case_exp->evaluate(scope)->getValue();
            if (Value::strict_equals(case_val, input)->isTrue())
            {
                // We found a match
                where = FOUND_IN_B;
                index = i;
                break;
            }
        }
    }

    // If not found yet, check for a default
    if (where == NOT_FOUND_YET && m_def)
    {
        where = FOUND_IN_DEFAULT;
    }

    // If no matches found, return (normal, empty, empty)
    // TODO: what's an empty value?
    Value* v = Null::null;
    if (where == NOT_FOUND_YET)
    {
        return new Completion(CT_NORMAL, v, Completion::empty);
    }

    // If FOUND_IN_A, evaluate all, fall through, and pretend we found
    // it in default
    if (where == FOUND_IN_A)
    {
        assert(m_accs);
        where = FOUND_IN_DEFAULT;
        for (int i = index; i < m_accs->size(); i++)
        {
            // 21. if we don't have a statement, continue
            Statement* stmt = m_accs->get(i)->get_statement();
            if (stmt)
            {
                // 22. Evaluate the statement list, and let r be the result
                Value* r = stmt->execute(scope);

                // 23. if r.value is not empty, the let v = r.value
                // TODO: what's empty?
                if (r->completion()->value()->getType() != VT_NULL)
                {
                    v = r->completion()->value();
                }

                // 24. If r is abrupt, return r.type, v, r.target
                if (r->completion()->type() != CT_NORMAL)
                {
                    return new Completion(r->completion()->type(),
                                v, r->completion()->target());
                }
            }
        }
    }

    // if FOUND_IN_DEFAULT, evaluate it, and pretend we found it
    // at the start of b.
    if (!m_def && where == FOUND_IN_DEFAULT)
    {
        where = FOUND_IN_B;
        index = 0;
    }
    if (where == FOUND_IN_DEFAULT)
    {
        assert(m_def);
        where = FOUND_IN_B;
        index = 0;

        // Evaluate the statement list, and let r be the result
        Value* r = m_def->execute(scope);

        // 23. if r.value is not empty, the let v = r.value
        // TODO: what's empty?
        if (r->completion()->value()->getType() != VT_NULL)
        {
            v = r->completion()->value();
        }

        // 24. If r is abrupt, return r.type, v, r.target
        if (r->completion()->type() != CT_NORMAL)
        {
            return new Completion(r->completion()->type(), v,
                    r->completion()->target());
        }
    }

    // If FOUND_IN_B, evaluate all, fall through.
    // We can stop pretending now.
    if (m_bccs && where == FOUND_IN_B)
    {
        for (int i = index; i < m_bccs->size(); i++)
        {
            // 21. if we don't have a statement, continue
            Statement* stmt = m_bccs->get(i)->get_statement();
            if (stmt)
            {
                // 22. Evaluate the statement list, and let r be the result
                Value* r = stmt->execute(scope);

                // 23. if r.value is not empty, the let v = r.value
                // TODO: what's empty?
                if (r->completion()->value()->getType() != VT_NULL)
                {
                    v = r->completion()->value();
                }

                // 24. If r is abrupt, return r.type, v, r.target
                if (r->completion()->type() != CT_NORMAL)
                {
                    return new Completion(r->completion()->type(),
                                v, r->completion()->target());
                }
            }
        }
    }

    // When in doubt, (normal, v, empty)
    return new Completion(CT_NORMAL, v, Completion::empty);
}

void SwitchStatement::getDeclaredVariables(std::vector<String*>& ids) const
{
    if (m_accs)
    {
        for (int i = 0; i < m_accs->size(); i++)
        {
            Statement* stmt = m_accs->get(i)->get_statement();
            if (stmt)
            {
                stmt->getDeclaredVariables(ids);
            }
        }
    }

    if (m_def)
    {
        m_def->getDeclaredVariables(ids);
    }

    if (m_bccs)
    {
        for (int i = 0; i < m_bccs->size(); i++)
        {
            Statement* stmt = m_bccs->get(i)->get_statement();
            if (stmt)
            {
                stmt->getDeclaredVariables(ids);
            }
        }
    }
}

void SwitchStatement::set_expression(Expression* exp)
{
    m_exp = exp;
}

